<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file contains the Collection class.
 *
 * PHP version 5
 *
 * This file is part of Majic - A rapid-development web application framework
 * for PHP 5.
 *
 * Majic is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * Majic is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Majic. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package     Collection
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 * @copyright   2007-2008 Michael J. I. Jackson
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
 * @version     SVN: $Id: Collection.php 37 2008-08-10 00:00:02Z mjijackson $
 */

/**
 * Enables type-safe collections of objects. Implements several interfaces that
 * make object collections behave like arrays.
 *
 * <code>
 * // Create a collection to hold 'person' objects
 * $coll = new Collection('person');
 *
 * // ArrayAccess
 * $coll[] = new Person('Michael', 'Jackson');
 *
 * // Iterator
 * foreach ($coll as $person) {
 *     echo $person->first_name;
 * }
 *
 * // Countable
 * echo count($coll);
 * </code>
 *
 * @package     Collection
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 */
class Collection implements Iterator, ArrayAccess, Countable
{

    /**
     * This collection's internal pointer.
     *
     * @var     int
     * @access  private
     */
    private $key;

    /**
     * This collection's type class name.
     *
     * @var     string
     * @access  private
     */
    private $type;

    /**
     * The container for this collection's objects.
     *
     * @var     array
     * @access  private
     */
    private $objects = array();

    /**
     * Constructor.
     *
     * @param   string  $type       The type class name this collection
     *                              contains objects of
     * @access  public
     */
    public function __construct($type)
    {
        $this->type = $type;
    }

    /**
     * Gets the name of the class this collection contains instances of.
     *
     * @return  string          The name of this collection's class
     * @access  public
     */
    public function type()
    {
        return $this->type;
    }

    /**
     * Gets the current element in the collection (according to the position of
     * its key).
     *
     * @return  mixed           The object at the current position
     * @access  public
     * @link    http://www.php.net/~helly/php/ext/spl/interfaceIterator.html Iterator
     */
    public function current()
    {
        return $this->offsetGet($this->key);
    }

    /**
     * Gets the value of this collection's key. Part of the Iterator interface.
     *
     * @return  int             The collection's key
     * @access  public
     * @link    http://www.php.net/~helly/php/ext/spl/interfaceIterator.html Iterator
     */
    public function key()
    {
        return $this->key;
    }

    /**
     * Gets the next element in this collection (according to the position of
     * its key).
     *
     * @return  mixed           The next element in the collection
     * @access  public
     * @link    http://www.php.net/~helly/php/ext/spl/interfaceIterator.html Iterator
     */
    public function next()
    {
        $next = $this->current();
        if ($next) {
            $this->key++;
        }
        return $next;
    }

    /**
     * Rewinds this collection's key to 0.
     *
     * @return  void
     * @access  public
     * @link    http://www.php.net/~helly/php/ext/spl/interfaceIterator.html Iterator
     */
    public function rewind()
    {
        $this->key = 0;
    }

    /**
     * Determines if the element at the current key position is valid.
     *
     * @return  bool            True if the current element is valid
     * @access  public
     * @link    http://www.php.net/~helly/php/ext/spl/interfaceIterator.html Iterator
     */
    public function valid()
    {
        return !is_null($this->current());
    }

    /**
     * Determines if the specified offset exists in this collection.
     *
     * @param   mixed   $offset     The offset to check
     * @return  bool                True if the specified offset exists
     * @access  public
     * @link    http://www.php.net/~helly/php/ext/spl/interfaceArrayAccess.html ArrayAccess
     */
    public function offsetExists($offset)
    {
        return isset($this->objects[$offset]);
    }

    /**
     * Gets the element at the specified offset in the collection.
     *
     * @param   mixed   $offset     The offset to get
     * @return  mixed               The element at the specified offset
     * @access  public
     * @link    http://www.php.net/~helly/php/ext/spl/interfaceArrayAccess.html ArrayAccess
     */
    public function offsetGet($offset)
    {
        return $this->offsetExists($offset) ? $this->objects[$offset] : null;
    }

    /**
     * Adds an element to this collection.
     *
     * @param   int         $offset     The offset to set
     * @param   mixed       $obj        The object to add
     * @return  void
     * @throws  Exception               If the given object does is not of
     *                                  the correct type for this collection
     * @access  public
     * @link    http://www.php.net/~helly/php/ext/spl/interfaceArrayAccess.html ArrayAccess
     */
    public function offsetSet($offset, $obj)
    {
        $obj_cls = $this->type();
        if (!($obj instanceof $obj_cls)) {
            $given = get_class($obj);
            throw new Exception("Expecting object type \"$obj_cls\", \"$given\" given");
        }

        if (is_null($offset)) {
            $offset = $this->count();
        }
        $this->objects[$offset] = $obj;
    }

    /**
     * Unsets the specified offset in this collection.
     *
     * @param   mixed   $offset     The offset to unset
     * @return  void
     * @access  public
     * @link    http://www.php.net/~helly/php/ext/spl/interfaceArrayAccess.html ArrayAccess
     */
    public function offsetUnset($offset)
    {
        if (is_int($offset)) {
            array_splice($this->objects, $offset, 1);
        } else {
            unset($this->objects[$offset]);
        }
    }

    /**
     * Gets the number of elements in this collection.
     *
     * @return  int         The number of elements
     * @access  public
     * @link    http://www.php.net/~helly/php/ext/spl/interfaceCountable.html Countable
     */
    public function count()
    {
        return count($this->objects);
    }

}

?>
